﻿#region << 版 本 注 释 >>
/*----------------------------------------------------------------
 * 版权所有 (c) 2022 北京超维景生物科技有限公司 保留所有权利。
 * CLR版本：4.0.30319.42000
 * 文件名：Kernel
 * 
 * 创建者：huangyang
 * 电子邮箱：huangyang@tvscope.cn
 * 创建时间：2023/2/8 19:18:50
 * 版本：V1.0.0
 * 描述：
 *
 * ----------------------------------------------------------------
 * 修改人：
 * 时间：
 * 修改说明：
 *
 * 版本：V1.0.1
 *----------------------------------------------------------------*/
#endregion << 版 本 注 释 >>

using ImageK.Process;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Channels;
using System.Threading.Tasks;

namespace ImageK.Util
{
    /** This class contains static utility methods. */
    public class Tools
    {
        /** This array contains the 16 hex digits '0'-'F'. */
        public static char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };

        /** Converts a Color to an 7 byte hex string starting with '#'. */
        public static string c2hex(Color c)
        {
            int i = c.ToArgb();
            char[] buf7 = new char[7];
            buf7[0] = '#';
            for (int pos = 6; pos >= 1; pos--)
            {
                buf7[pos] = hexDigits[i & 0xf];
                i >>= 4;
            }
            return new string(buf7);
        }

        /** Converts a float to an 9 byte hex string starting with '#'. */
        public static string f2hex(float f)
        {
            int i = BitConverter.SingleToInt32Bits(f);
            char[] buf9 = new char[9];
            buf9[0] = '#';
            for (int pos = 8; pos>=1; pos--)
            {
                buf9[pos] = hexDigits[i&0xf];
                i >>= 4;
            }
            return new string(buf9);
        }

        /** Converts an int to a zero-padded hex string of fixed length 'digits'.
         *  If the number is too high, it gets truncated, keeping only the lowest 'digits' characters. */
        public static String int2hex(int i, int digits)
        {
            char[] buf = new char[digits];
            for (int pos = buf.Length-1; pos>=0; pos--)
            {
                buf[pos] = hexDigits[i&0xf];
                i >>= 4;
            }
            return new string(buf);
        }

        public static ImageStatistics getStatistics(double[] a)
        {
            ImageProcessor ip = new FloatProcessor(a.Length, 1, a);
            return ip.getStats();
        }

        /** Determines the minimum and maximum value in the array <code>a</code>
        *  and returns them as 2-element array {minimum, maximum}. */
        public static double[] getMinMax(double[] a)
        {
            double min = Double.NaN;
            double max = Double.NaN;
            int i = 0;
            for (; i<a.Length; i++)
                if (!Double.IsNaN(a[i]))
                    break;
            if (i<a.Length)
            {
                min = a[i];
                max = a[i];
            }
            for (; i<a.Length; i++)
            {
                double value = a[i];
                if (value<min)
                    min = value;
                else if (value>max)
                    max = value;
            }
            double[] minAndMax = new double[2];
            minAndMax[0] = min;
            minAndMax[1] = max;
            return minAndMax;
        }

        /** Determines the minimum and maximum value in the array <code>a</code>
        *  and returns them as 2-element array {minimum, maximum}. */
        public static double[] getMinMax(float[] a)
        {
            float min = float.NaN;
            float max = float.NaN;
            int i = 0;
            for (; i<a.Length; i++)
                if (!float.IsNaN(a[i]))
                    break;
            if (i<a.Length)
            {
                min = a[i];
                max = a[i];
            }
            for (; i<a.Length; i++)
            {
                float value = a[i];
                if (value<min)
                    min = value;
                else if (value>max)
                    max = value;
            }
            double[] minAndMax = new double[2];
            minAndMax[0] = min;
            minAndMax[1] = max;
            return minAndMax;
        }

        /** Converts the float array 'a' to a double array. */
        public static double[] toDouble(float[] a)
        {
            int len = a.Length;
            double[] d = new double[len];
            for (int i = 0; i<len; i++)
                d[i] = a[i];
            return d;
        }

        /** Converts the double array 'a' to a float array. */
        public static float[] toFloat(double[] a)
        {
            if (a==null)
                return null;
            int len = a.Length;
            float[] f = new float[len];
            for (int i = 0; i<len; i++)
                f[i] = (float)a[i];
            return f;
        }

        /** Adds a number to all array elements */
        public static void addToArray(float[] a, float value)
        {
            for (int i = 0; i<a.Length; i++)
                a[i] += value;
        }

        /** Converts carriage returns to line feeds. */
        public static string fixNewLines(string s)
        {
            if (s==null)
                return null;
            char[] chars = s.ToCharArray();
            for (int i = 0; i<chars.Length; i++)
            { if (chars[i]=='\r') chars[i] = '\n'; }
            return new string(chars);
        }

        /**
        * Returns a double containg the value represented by the
        * specified <code>String</code>.
        *
        * @param      s   the string to be parsed.
        * @param      defaultValue   the value returned if <code>s</code>
        *	does not contain a parsable double
        * @return     The double value represented by the string argument or
        *	<code>defaultValue</code> if the string does not contain a parsable double
        */
        public static double parseDouble(string s, double defaultValue)
        {
            if (s == null)
                return defaultValue;
            try
            {
                defaultValue = double.Parse(s);
            }
            catch (Exception e) { }
            return defaultValue;
        }

        /**
        * Returns a double containg the value represented by the
        * specified <code>String</code>.
        *
        * @param      s   the string to be parsed.
        * @return     The double value represented by the string argument or
        *	Double.NaN if the string does not contain a parsable double
        */
        public static double parseDouble(string s)
        {
            return parseDouble(s, Double.NaN);
        }

        /** Returns the number of decimal places needed to display a
	    number, or -2 if exponential notation should be used. */
        public static int getDecimalPlaces(double n)
        {
            if ((int)n == n || Double.IsNaN(n))
                return 0;
            String s = "" + n;
            if (s.Contains("E"))
                return -2;
            while (s.EndsWith("0"))
                s = s.Substring(0, s.Length - 1);
            int index = s.IndexOf(".");
            if (index == -1) return 0;
            int digits = s.Length - index - 1;
            if (digits > 4) digits = 4;
            return digits;
        }

        /** Returns the number of decimal places needed to display two numbers,
			or -2 if exponential notation should be used. */
        public static int getDecimalPlaces(double n1, double n2)
        {
            if ((int)n1 == n1 && (int)n2 == n2)
                return 0;
            int digits = getDecimalPlaces(n1);
            int digits2 = getDecimalPlaces(n2);
            if (digits == 0)
                return digits2;
            if (digits2 == 0)
                return digits;
            if (digits < 0 || digits2 < 0)
                return digits;
            if (digits2 > digits)
                digits = digits2;
            return digits;
        }


        /** Splits a string into substrings using the default delimiter set,
	which is " \t\n\r" (space, tab, newline and carriage-return). */
        public static String[] split(String str)
        {
            // return split(str, " \t\n\r");
            return str.Split(new char[] { ' ', '\t','\n','\r' }); 
        }
        
        /** Splits a string into substring using the characters
        contained in the second argument as the delimiter set. */
        public static String[] split(String str, String delim)
        {
            return str.Split(delim.ToCharArray());
            // if (delim.equals("\n"))
            //     return splitLines(str);
            // StringTokenizer t = new StringTokenizer(str, delim);
            // int tokens = t.countTokens();
            // String[] strings;
            // if (tokens>0)
            // {
            //     strings = new String[tokens];
            //     for (int i = 0; i<tokens; i++)
            //         strings[i] = t.nextToken();
            // }
            // else
            //     strings = new String[0];
            // return strings;
        }
        //
        // static String[] splitLines(String str)
        // {
        //     Vector v = new Vector();
        //     try
        //     {
        //         BufferedReader br = new BufferedReader(new StringReader(str));
        //         String line;
        //         while (true)
        //         {
        //             line = br.readLine();
        //             if (line == null) break;
        //             v.addElement(line);
        //         }
        //         br.close();
        //     }
        //     catch (Exception e) { }
        //     String[] lines = new String[v.size()];
        //     v.copyInto((String[])lines);
        //     return lines;
        // }

        /** Returns a sorted list of indices of the specified double array.
            Modified from: http://stackoverflow.com/questions/951848 by N.Vischer.
        */
        public static int[] rank(double[] values)
        {
            int n = values.Length;
            int[] indexes = new int[n];
            double[] data = new double[n];
            for (int i = 0; i<n; i++)
            {
                indexes[i] = i;
                data[i] = values[i];
            }
            Array.Sort(indexes, new IntegerComparator<double>(data, (data1, data2) => data1.CompareTo(data2)));
		    int[] indexes2 = new int[n];
		    for (int i=0; i<n; i++)
			    indexes2[i] = indexes[i];
		    return indexes2;
	    }

        /** Returns a sorted list of indices of the specified String array. */
        public static int[] rank(string[] data)
        {
            int n = data.Length;
            int[] indexes = new int[n];
            for (int i = 0; i<n; i++)
                indexes[i] = i;
            Array.Sort(indexes, new IntegerComparator<string>(data, (data1, data2) =>
            {
                return string.Compare(data1, data2, StringComparison.OrdinalIgnoreCase);
            }));
		    int[] indexes2 = new int[n];
		    for (int i=0; i<n; i++)
			    indexes2[i] = indexes[i];
		    return indexes2;
	    }

        /** Returns an array linearly resampled to a different length. */
        public static double[] resampleArray(double[] y1, int len2)
        {
            int len1 = y1.Length;
            double factor = (double)(len2-1)/(len1-1);
            double[] y2 = new double[len2];
            if (len1 == 0)
            {
                return y2;
            }
            if (len1 == 1)
            {
                for (int jj = 0; jj<len2; jj++)
                    y2[jj] = y1[0];
                return (y2);
            }
            double[] f1 = new double[len1];//fractional positions
            double[] f2 = new double[len2];
            for (int jj = 0; jj<len1; jj++)
                f1[jj] = jj*factor;
            for (int jj = 0; jj<len2; jj++)
                f2[jj] = jj/factor;
            for (int jj = 0; jj<len2-1; jj++)
            {
                double pos = f2[jj];
                int leftPos = (int)Math.Floor(pos);
                int rightPos = (int)Math.Floor(pos)+1;
                double fraction = pos-Math.Floor(pos);
                double value = y1[leftPos] + fraction*(y1[rightPos]-y1[leftPos]);
                y2[jj] = value;
            }
            y2[len2-1] = y1[len1-1];
            return y2;
        }


// /** Opens a text file in ij.jar as a String (example path: "/macros/Circle_Tool.txt"). */
// public static String openFromIJJarAsString(String path)
// {
//     return (new ij.plugin.MacroInstaller()).openFromIJJar(path);
// }
//
// /** Copies the contents of the file at 'path1' to 'path2', returning an error message
//     (as a non-empty string) if there is an error. Based on the method with the
//     same name in Tobias Pietzsch's TifBenchmark class.
// */
// public static String copyFile(String path1, String path2)
// {
//     File f1 = new File(path1);
//     File f2 = new File(path2);
//     try
//     {
//         if (!f1.exists())
//             return "Source file does not exist";
//         if (!f2.exists())
//             f2.createNewFile();
//         long time = f1.lastModified();
//         FileInputStream stream1 = new FileInputStream(f1);
//         FileChannel channel1 = stream1.getChannel();
//         FileOutputStream stream2 = new FileOutputStream(f2);
//         final FileChannel channel2 = stream2.getChannel();
//         if (channel2!=null && channel1!=null)
//             channel2.transferFrom(channel1, 0, channel1.size());
//         channel1.close();
//         stream1.close();
//         channel2.close();
//         stream2.close();
//         f2.setLastModified(time);
//     }
//     catch (Exception e)
//     {
//         return e.getMessage();
//     }
//     return "";
// }

        /** Retrieves a number form a list of key-number pairs like "value1=1234.5 area=1.2e6".
         *  The "=" (if present) must be part of the 'key' string. Delimiters may be commas, semicolons or whitespace.
         *  There must be no whitespace between key and number.
         *  Returns Double.NaN if 'list' is null, if the key is not found, if the number is 'NaN' or invalid. */
        public static double getNumberFromList(String list, String key)
        {
            return getNumberFromList(list, key, Double.NaN);
        }

        public static double getNumberFromList(String list, String key, double defaultValue)
        {
            if (list == null) return defaultValue;
            int i = list.IndexOf(key);
            if (i < 0) return defaultValue;
            int start = i + key.Length;
            int n = start;
            while (n < list.Length && !isDelimiter(list[n])) n++;
            double value = parseDouble(list.Substring(start, n));
            return Double.IsNaN(value) ? defaultValue : value;
        }

        /** Retrieves a String form a list of key-number pairs like "value1="abc" str='the Text'".
         *  The "=" (if present) must be part of the 'key' string.
         *  Strings containing commas, semicolons or whitespace must be enclosed in single or double quotes.
         *  There must be no whitespace between key and quotes or key and unquoted String.
         *  Strings must not contain a key. In case of doubt, e.g., replace the '=' with '\='
         *  Escape sequences with backslashes are recognized, also backslash-u???? for unicode.
         *  Returns null if 'list' is null or if the key is not found */
        public static String getStringFromList(String list, String key)
        {
            return getStringFromList(list, key, null);
        }

        public static String getStringFromList(String list, String key, String defaultValue)
        {
            if (list == null) return defaultValue;
            int i = list.IndexOf(key);
            if (i < 0) return defaultValue;
            int start = i + key.Length;
            if (list.Length == start) return "";
            char quote = list[start];
            bool hasQuotes = quote == '\'' || quote == '\"';
            if (hasQuotes) start++;
            String str = decodeEscaped(list.Substring(start), hasQuotes ? quote : '\0');
            if (str==null)
                str = defaultValue;
            return str;
        }

        /** Decodes backslash-escaped characters from the String until the character 'delim' is found.
         *  With 'delim'=-1, does not search for a delimiter, with 'delim'=(char)-2, (unescaped)
         *  whitespace, commas and semicolons are interpreted as delimiters.
         *  Also decodes unicode backslash-u???? characters */
        public static String decodeEscaped(String str, char delim)
        {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i<str.Length; i++)
            {
                char c = str[i];
                //将(char)-2替换为'\0'，表示空字符
                if ((delim == '\0' && isDelimiter(c)) || c == delim) break;
                // if ((delim == (char)-2 && isDelimiter(c)) || c == delim) break;
                if (c == '\\' && i+1 < str.Length)
                {    //escaped by backslash
                    i++;
                    c = str[i];
                    if (c == 'u' && i+4 < str.Length)
                        try
                        {
                            c = (char)int.Parse(str.Substring(i+1, i+5), System.Globalization.NumberStyles.HexNumber);
                            i += 4;
                        }
                        catch (FormatException e) { }
                    else
                        c = withBackslash(c);           // decodes backslash-t for tab etc.
                }
                sb.Append(c);
            }
            return sb.ToString();
        }

        private static bool isDelimiter(char c)
        {
            return char.IsWhiteSpace(c) || c==',' || c==';';
        }

        /** Returns the escaped character if there was a preceding backslash.
         *  Returns 'c' if there is no known escape sequence backslash-c */
        private static char withBackslash(char c)
        {
            switch (c)
            {
                case 'b': return '\b';
                case 't': return '\t';
                case 'f': return '\f';
                case 'r': return '\r';
                case 'n': return '\n';
                default: return c;
            }
        }

        /** Returns the checksum of a string or file, or "0" if no success.
        The 'method' argument must be "MD5" or "SHA-256".
        */
        // public static String getHash(String method, bool fromFile, String pathOrString)
        // {
        //     method = method.ToUpper();
        //     bool md5 = method.Contains("MD5");
        //     bool sha_256 = method.Contains("SHA-256");
        //     try
        //     {
        //         MessageDigest digest = null;
        //         if (md5)
        //             digest = MessageDigest.getInstance("MD5");
        //         else if (sha_256)
        //             digest = MessageDigest.getInstance("SHA-256");
        //         else
        //             return "0";
        //         Path path = Paths.get(pathOrString);
        //         byte[] encodedhash;
        //         if (fromFile)
        //             encodedhash = digest.digest(Files.readAllBytes(path));
        //         else
        //             encodedhash = digest.digest(pathOrString.getBytes());
        //
        //         return bytesToHex(encodedhash);
        //
        //     }
        //     catch (Exception e) { }
        //     return "0";
        // }

        private static String bytesToHex(byte[] hash)
        {
            StringBuilder sb = new StringBuilder();
            foreach (byte b in hash)
            {
                sb.Append(b.ToString("x2"));
            }
            return sb.ToString();
        }
    }

    class IntegerComparator<T> : IComparer<int>
    {
        private T[] data;
        private Func<T, T, int> compareFunc;

        public IntegerComparator(T[] data, Func<T,T, int> compareFunc)
        {
            this.data = data;
            this.compareFunc = compareFunc;
        }

        public int Compare(int o1, int o2)
        {
            // return string.Compare(data[o1], data[o2], StringComparison.OrdinalIgnoreCase);
            return compareFunc(data[o1], data[o2]);
        }
    }
}
